
#include "ssp.h"


void pxa_set_cken(int clock, int enable)
{
//	unsigned long flags;
	int hsio2_enable = 0;

	switch (clock) {
		/*special case1:*/
		case CKEN_AC97:
			if (enable) {
				CKENA |= (0x1u << clock);
				if (CKEN_AC97 == clock) {
				/*
				 * REVISIT: Make out-clock for AC97 24.576MHz
				 * This is workaround for that AC97 clock is
				 * not correct after reset.
				 */
					AC97_DIV = 1625<<12 | 128;
				}
			} else {
				CKENA &= ~(0x1u << clock);
			}
			break;

		/*special case2:*/
		case CKEN_USB2:
			if (enable) {
				CKENA |= (0x1u << clock);
			} else {
				CKENA &= ~(0x1u << clock);
			}
		case CKEN_GRAPHICS:
			if (CKEN_GRAPHICS < 32) {
				if (enable) {
					CKENA |= (0x1u << clock);
				} else {
					CKENA &= ~(0x1u << clock);
				}
			} else {
				if (enable) {
					CKENB |= (0x1u << (clock - 32));
				} else {
					CKENB &= ~(0x1u << (clock - 32));
				}
			}
		case CKEN_MVED:
			if (enable) {
				CKENB |= (0x1u << (clock-32) );
			} else {
				CKENB &= ~(0x1u << (clock - 32));
			}
		case CKEN_HSIO2:
			if (enable)
				hsio2_enable++;
			if (CKENA & (0x1u << CKEN_USB2))
				hsio2_enable++;
    		if (CKENB & (0x1u << (CKEN_GRAPHICS - 32)))
    			hsio2_enable++;
			if (CKENB & (0x1u << (CKEN_MVED - 32)))
				hsio2_enable++;

			if (hsio2_enable)
				CKENB |= (0x1u << (CKEN_HSIO2-32) );
			else
				CKENB &= ~(0x1u << (CKEN_HSIO2-32) );
			break;

		default:	/*normal case:*/
			if (clock < 32) {
				if (enable) {
					CKENA |= (0x1u << clock);
					//printf("enable default clock CKENA 0x%08x\n",CKENA);
				} else {
					CKENA &= ~(0x1u << clock);
				}
			} else {
				if (enable) {
					CKENB |= (0x1u << (clock - 32) );
				} else {
					CKENB &= ~(0x1u << (clock - 32));
				}
			}
			break;
	}

}


#define PXA_SSP_PORTS 	4

#define TIMEOUT 100000

struct ssp_info_ {
	int irq;
	u32 clock;
};

/*
 * SSP port clock and IRQ settings
 */
#define PXA_IRQ(x)	((x) - 0)
#define	IRQ_SSP		PXA_IRQ(24)	/* SSP Service Request */
#define IRQ_SSP2	 PXA_IRQ(16)	/* SSP2 interrupt (PXA27x) */
#define IRQ_SSP3	 PXA_IRQ(0)	/* SSP3 service request */

static const struct ssp_info_ ssp_info[PXA_SSP_PORTS] = {
	{IRQ_SSP,	CKEN_SSP1},
	{IRQ_SSP2,	CKEN_SSP2},
	{IRQ_SSP3,	CKEN_SSP3},
};

static int use_count[PXA_SSP_PORTS] = {0, 0, 0};

/**
 * ssp_write_word - write a word to the SSP port
 * @data: 32-bit, MSB justified data to write.
 *
 * Wait for a free entry in the SSP transmit FIFO, and write a data
 * word to the SSP port.
 *
 * The caller is expected to perform the necessary locking.
 *
 * Returns:
 *   %-ETIMEDOUT	timeout occurred
 *   0			success
 */
int ssp_write_word(struct ssp_dev *dev, u32 data)
{
	int timeout = TIMEOUT;

	while (!(SSSR_P(dev->port) & SSSR_TNF)) {
	        if (!--timeout)
	        	return -1;
	//	cpu_relax();
	}
	SSDR_P(dev->port) = data;

	return 0;
}

/**
 * ssp_read_word - read a word from the SSP port
 *
 * Wait for a data word in the SSP receive FIFO, and return the
 * received data.  Data is LSB justified.
 *
 * Note: Currently, if data is not expected to be received, this
 * function will wait for ever.
 *
 * The caller is expected to perform the necessary locking.
 *
 * Returns:
 *   %-ETIMEDOUT	timeout occurred
 *   32-bit data	success
 */
int ssp_read_word(struct ssp_dev *dev, u32 *data)
{
	int timeout = TIMEOUT;

	while (!(SSSR_P(dev->port) & SSSR_RNE)) {
	        if (!--timeout)
	        	return -1;
//		cpu_relax();
	}

	*data = SSDR_P(dev->port);
	return 0;
}

/**
 * ssp_flush - flush the transmit and receive FIFOs
 *
 * Wait for the SSP to idle, and ensure that the receive FIFO
 * is empty.
 *
 * The caller is expected to perform the necessary locking.
 */
int ssp_flush(struct ssp_dev *dev)
{
	int timeout = TIMEOUT * 2;

	/* Verify TX FIFO is empty */
	while(((SSSR_P(dev->port) & 0x00000F00) && /* TFL - Transmit FIFO Level*/
				(SSSR_P(dev->port) & SSSR_TNF))||
			(!(SSSR_P(dev->port) & 0x00000F00) &&
			 !(SSSR_P(dev->port) & SSSR_TNF)));

	do {
		while (SSSR_P(dev->port) & SSSR_RNE) {
		        if (!--timeout)
		        	return -1;
			(void) SSDR_P(dev->port);
		}
	        if (!--timeout)
	        	return -1;
	} while (SSSR_P(dev->port) & SSSR_BSY);

	return 0;
}

/**
 * ssp_enable - enable the SSP port
 *
 * Turn on the SSP port.
 */
void ssp_enable(struct ssp_dev *dev)
{
	SSCR0_P(dev->port) |= SSCR0_SSE;
}

/**
 * ssp_disable - shut down the SSP port
 *
 * Turn off the SSP port, optionally powering it down.
 */
void ssp_disable(struct ssp_dev *dev)
{
	SSCR0_P(dev->port) &= ~SSCR0_SSE;
}

/**
 * ssp_save_state - save the SSP configuration
 * @ssp: pointer to structure to save SSP configuration
 *
 * Save the configured SSP state for suspend.
 */
void ssp_save_state(struct ssp_dev *dev, struct ssp_state *ssp)
{
	ssp->cr0 = SSCR0_P(dev->port);
	ssp->cr1 = SSCR1_P(dev->port);
	ssp->to = SSTO_P(dev->port);
	ssp->psp = SSPSP_P(dev->port);

	SSCR0_P(dev->port) &= ~SSCR0_SSE;
}

/**
 * ssp_restore_state - restore a previously saved SSP configuration
 * @ssp: pointer to configuration saved by ssp_save_state
 *
 * Restore the SSP configuration saved previously by ssp_save_state.
 */
void ssp_restore_state(struct ssp_dev *dev, struct ssp_state *ssp)
{
	SSSR_P(dev->port) = SSSR_ROR | SSSR_TUR | SSSR_BCE;

	SSCR0_P(dev->port) = ssp->cr0 & ~SSCR0_SSE;
	SSCR1_P(dev->port) = ssp->cr1;
	SSTO_P(dev->port) = ssp->to;
	SSPSP_P(dev->port) = ssp->psp;

	SSCR0_P(dev->port) = ssp->cr0;
}

/**
 * ssp_config - configure SSP port settings
 * @mode: port operating mode
 * @flags: port config flags
 * @psp_flags: port PSP config flags
 * @speed: port speed
 *
 * Port MUST be disabled by ssp_disable before making any config changes.
 */
int ssp_config(struct ssp_dev *dev, u32 mode, u32 flags, u32 psp_flags, u32 speed)
{
	dev->mode = mode;
	dev->flags = flags;
	dev->psp_flags = psp_flags;
	dev->speed = speed;

	/* set up port type, speed, port settings */
	SSCR0_P(dev->port) = (dev->speed | dev->mode);
	SSCR1_P(dev->port) = dev->flags;
	SSPSP_P(dev->port) = dev->psp_flags;

	return 0;
}

/**
 * ssp_init - setup the SSP port
 *
 * initialise and claim resources for the SSP port.
 *
 * Returns:
 *   %-ENODEV	if the SSP port is unavailable
 *   %-EBUSY	if the resources are already in use
 *   %0		on success
 */
int ssp_init(struct ssp_dev *dev, u32 port, u32 init_flags)
{
	use_count[port - 1]++;
	dev->port = port;
	dev->irq = 0;

	/* turn on SSP port clock */
	pxa_set_cken(ssp_info[port-1].clock, 1);
	return 0;
}

/**
 * ssp_exit - undo the effects of ssp_init
 *
 * release and free resources for the SSP port.
 */
void ssp_exit(struct ssp_dev *dev)
{
	SSCR0_P(dev->port) &= ~SSCR0_SSE;

	pxa_set_cken(ssp_info[dev->port-1].clock, 0);
//	if (dev->irq)
//		free_irq(dev->irq, dev);
//	release_mem_region(__PREG(SSCR0_P(dev->port)), 0x2c);
	use_count[dev->port - 1]--;
}

